#include <linux/slab.h>
#include <linux/kref.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include <linux/mutex.h>

#define err(format, arg...) printk(KERN_ERR KBUILD_MODNAME ": " \
	format "\n" , ## arg)
#define info(format, arg...) printk(KERN_INFO KBUILD_MODNAME ": " \
	format "\n" , ## arg)
#define warn(format, arg...) printk(KERN_WARNING KBUILD_MODNAME ": " \
	format "\n" , ## arg)
	
#define usb_struct_VENDOR_ID	0x0fc5
#define usb_struct_PRODUCT_ID	0x1223

static struct usb_device_id m_table [] = {
	{ USB_DEVICE(usb_struct_VENDOR_ID, usb_struct_PRODUCT_ID) },
	{ }
};
MODULE_DEVICE_TABLE(usb, m_table);

#define usb_struct_MINOR_BASE	0

struct usb_struct {
	struct usb_device	*udev;			/* the usb device for this device */
	struct usb_interface	*interface;		/* the interface for this device */
	struct usb_anchor	submitted;		/* in case we need to retract our submissions */
	int			errors;			/* the last request tanked */
	int			open_count;		/* count the number of openers */
	spinlock_t		err_lock;		/* lock for errors */
	struct kref		kref;
	struct mutex		io_mutex;		/* synchronize I/O with disconnect */
};
#define to_usb_struct(d) container_of(d, struct usb_struct, kref)

static struct usb_driver m_driver;
static void m_draw_down(struct usb_struct *dev);

static void m_delete(struct kref *kref)
{
	struct usb_struct *dev = to_usb_struct(kref);
	usb_put_dev(dev->udev);
	kfree(dev);
}

static int m_open(struct inode *inode, struct file *file)
{
	struct usb_struct *dev;
	struct usb_interface *interface;
	int subminor;
	int retval = 0;

	subminor = iminor(inode);

	interface = usb_find_interface(&m_driver, subminor);
	if (!interface) {
		err ("%s - error, can't find device for minor %d",
		     __func__, subminor);
		retval = -ENODEV;
		goto exit;
	}

	dev = usb_get_intfdata(interface);
	if (!dev) {
		retval = -ENODEV;
		goto exit;
	}

	kref_get(&dev->kref);

	mutex_lock(&dev->io_mutex);

	if (!dev->open_count++) {
		retval = usb_autopm_get_interface(interface);
			if (retval) {
				dev->open_count--;
				mutex_unlock(&dev->io_mutex);
				kref_put(&dev->kref, m_delete);
				goto exit;
			}
	}

	file->private_data = dev;
	mutex_unlock(&dev->io_mutex);

exit:
	return retval;
}

static int m_release(struct inode *inode, struct file *file)
{
	struct usb_struct *dev;

	dev = (struct usb_struct *)file->private_data;
	if (dev == NULL)
		return -ENODEV;

	mutex_lock(&dev->io_mutex);
	if (!--dev->open_count && dev->interface)
		usb_autopm_put_interface(dev->interface);
	mutex_unlock(&dev->io_mutex);

	kref_put(&dev->kref, m_delete);
	return 0;
}

static int m_flush(struct file *file, fl_owner_t id)
{
	struct usb_struct *dev;
	int res;

	dev = (struct usb_struct *)file->private_data;
	if (dev == NULL)
		return -ENODEV;

	mutex_lock(&dev->io_mutex);
	m_draw_down(dev);

	spin_lock_irq(&dev->err_lock);
	res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;
	dev->errors = 0;
	spin_unlock_irq(&dev->err_lock);

	mutex_unlock(&dev->io_mutex);

	return res;
}

static ssize_t m_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
	const char ptr[]=
		"Usage: \nWrite one of the following strings to me.\n"
		"blueon redon greenon\nblueoff redoff greenoff\n"
		"blueflash redflash greenflash\n"
		"blueontime<SPACE><Integer[0,255]>\n"
		"redontime<SPACE><Integer[0,255]>\n"
		"greenontime<SPACE><Integer[0,255]>\n"
		"blueofftime<SPACE><Integer[0,255]>\n"
		"redofftime<SPACE><Integer[0,255]>\n"
		"greenofftime<SPACE><Integer[0,255]>\n"
		"flashfreq<SPACE><Integer[0,255]>\n"
		"bluelightness<SPACE><Integer[0,255]>\n"
		"redlightness<SPACE><Integer[0,255]>\n"
		"greenlightness<SPACE><Integer[0,255]>\n"
		"loudspeaker<SPACE><Integer[0,255]><SPACE><Integer[0,255]>\n"
		; 
	ssize_t sz = sizeof(ptr);
	if(*offp >= 0 && *offp < sz){
		int ret = sz - *offp;
		if (copy_to_user(buff + *offp, ptr + *offp, ret))
			return -EFAULT;
		else{
			*offp = sz;
			return ret;
		}
	}else
		return 0;
}

static int ctl(struct usb_device *udev, char cmd[], int arg, int arg2){
	int pipe=usb_sndctrlpipe(udev,0);
	#define EQ(standard, input) (!strncmp(standard,input,sizeof(standard)-1))
	#define CM(request, requesttype, value, index) usb_control_msg(udev,pipe,request,requesttype,value,index,NULL,0,2000)
	if(EQ("blueontime", cmd)){
		CM(12,0x48,0x170a,(arg<<8)+0x64);
	}else if(EQ("redontime", cmd)){
		CM(12,0x48,0x160a,(arg<<8)+0x64);
	}else if(EQ("greenontime", cmd)){
		CM(12,0x48,0x150a,(arg<<8)+0x64);
	}else if(EQ("blueofftime", cmd)){
		CM(12,0x48,0x170a,arg+0x6400);
	}else if(EQ("redofftime", cmd)){
		CM(12,0x48,0x160a,arg+0x6400);
	}else if(EQ("greenofftime", cmd)){
		CM(12,0x48,0x150a,arg+0x6400);
	}else if(EQ("blueon", cmd)){
		CM(12,0x48,0x140a,4);
		CM(12,0x48,0x0c0a,4);
	}else if(EQ("redon", cmd)){
		CM(12,0x48,0x140a,2);
		CM(12,0x48,0x0c0a,2);
	}else if(EQ("greenon", cmd)){
		CM(12,0x48,0x140a,1);
		CM(12,0x48,0x0c0a,1);
	}else if(EQ("blueoff", cmd)){
		CM(12,0x48,0x140a,4);
		CM(12,0x48,0x0c0a,0x400);
	}else if(EQ("redoff", cmd)){
		CM(12,0x48,0x140a,2);
		CM(12,0x48,0x0c0a,0x200);
	}else if(EQ("greenoff", cmd)){
		CM(12,0x48,0x140a,1);
		CM(12,0x48,0x0c0a,0x100);
	}else if(EQ("blueflash", cmd)){
		CM(12,0x48,0x140a,4);
		CM(12,0x48,0x0c0a,0x400);
		CM(12,0x48,0x140a,0x400);
	}else if(EQ("redflash", cmd)){
		CM(12,0x48,0x140a,2);
		CM(12,0x48,0x0c0a,0x200);
		CM(12,0x48,0x140a,0x200);
	}else if(EQ("greenflash", cmd)){
		CM(12,0x48,0x140a,1);
		CM(12,0x48,0x0c0a,0x100);
		CM(12,0x48,0x140a,0x100);
	}else if(EQ("flashfreq", cmd)){
		CM(12,0x48,0x130a,arg);
	}else if(EQ("bluelightness", cmd)){
		CM(12,0x48,0x220a,(arg<<8)+2);
	}else if(EQ("redlightness", cmd)){
		CM(12,0x48,0x220a,(arg<<8)+1);
	}else if(EQ("greenlightness", cmd)){
		CM(12,0x48,0x220a,(arg<<8)+0);
	}else if(EQ("loudspeaker", cmd)){
		unsigned char buffer[8]={arg2,1,1,0,0,0,0,0};
		usb_control_msg(udev,pipe,12,8,0x460a,(arg<<8)+1,buffer,8,2000);
		CM(12,0x48,0x460a,(arg<<8)+1);
	}else{
		printk("<3>USBLED: unknown command %s\n", cmd);
		return -1;
	}
	return 0;
}

static ssize_t m_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
{
	struct usb_struct *dev = (struct usb_struct *)filp->private_data;
	int retval = 0;
	char buffer[100], cmd[100];
	int arg=0, arg2=0;

	if (count==0) return 0;
	if(count>=sizeof(buffer))
		count = sizeof(buffer)-1;
	if (copy_from_user(buffer, buff, count))
		return -EFAULT;
	*offp += count;
	sscanf(buffer, "%s%d%d", cmd, &arg, &arg2);
	mutex_lock(&dev->io_mutex);
	if (!dev->interface) {		/* disconnect() was called */
		mutex_unlock(&dev->io_mutex);
		return -ENODEV;
	}
	retval =  ctl(dev->udev, cmd, arg, arg2);
	mutex_unlock(&dev->io_mutex);
	if(retval) return -EINVAL;
	return count;
}

static const struct file_operations m_fops = {
	.owner =	THIS_MODULE,
	.read =		m_read,
	.write =	m_write,
	.open =		m_open,
	.release =	m_release,
	.flush =	m_flush,
};

static struct usb_class_driver m_class = {
	.name =		"led%d",
	.fops =		&m_fops,
	.minor_base =	usb_struct_MINOR_BASE,
};

static int m_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
	struct usb_struct *dev;
	int retval = -ENOMEM;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev) {
		err("Out of memory");
		goto error;
	}
	kref_init(&dev->kref);
	mutex_init(&dev->io_mutex);
	spin_lock_init(&dev->err_lock);
	init_usb_anchor(&dev->submitted);

	dev->udev = usb_get_dev(interface_to_usbdev(interface));
	dev->interface = interface;

	usb_set_intfdata(interface, dev);

	retval = usb_register_dev(interface, &m_class);
	if (retval) {
		err("Not able to get a minor for this device.");
		usb_set_intfdata(interface, NULL);
		goto error;
	}

	info("Use file /dev/%s", m_class.name);
	return 0;

error:
	if (dev)
		kref_put(&dev->kref, m_delete);
	return retval;
}

static void m_disconnect(struct usb_interface *interface)
{
	struct usb_struct *dev;

	dev = usb_get_intfdata(interface);
	usb_set_intfdata(interface, NULL);

	usb_deregister_dev(interface, &m_class);

	mutex_lock(&dev->io_mutex);
	dev->interface = NULL;
	mutex_unlock(&dev->io_mutex);

	usb_kill_anchored_urbs(&dev->submitted);

	kref_put(&dev->kref, m_delete);
}

static void m_draw_down(struct usb_struct *dev)
{
	int time;
	time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
	if (!time)
		usb_kill_anchored_urbs(&dev->submitted);
}

static struct usb_driver m_driver = {
	.name =		"9263_test_driver",
	.probe =	m_probe,
	.disconnect =	m_disconnect,
	.id_table =	m_table,
};

static int __init m_module_init(void)
{
	return usb_register(&m_driver);
}

static void __exit m_module_exit(void)
{
	usb_deregister(&m_driver);
}

module_init(m_module_init);
module_exit(m_module_exit);

MODULE_LICENSE("GPL");
